﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Obsidian.GameObjects;
using System.Collections;

namespace Obsidian.Components
{

    /// <summary>
    /// The coordinate space in which to operate.
    /// </summary>
    public enum Space
    {
        /// <summary>
        /// Applies transformation relative to the world coordinate system
        /// </summary>
        World, 

        /// <summary>
        /// Applies transformation relative to the local coordinate system
        /// </summary>
        Self,
    }


    public sealed class Transform : Component, IEnumerable<Transform>
    {
        /// <summary>
        /// Position of the transform relative to the parent transform.
        /// </summary>
        public Vector2 LocalPosition { get; set; }

        /// <summary>
        /// The rotation of the transform relative to the parent transform's rotation.
        /// </summary>
        public float LocalRotation { get; set; }

        /// <summary>
        /// The scale of the transform relative to the parent.
        /// </summary>
        public Vector2 LocalScale { get; set; }

        /// <summary>
        /// The position of the transform in world space.
        /// </summary>
        public Vector2 Position
        {
            get
            {
                if (Parent != null)
                {
                    // we need to get the parents Position and add our LocalPositon to it, while accounting for rotation and scaling
                    return Vector2.Transform(LocalPosition, LocalToWorldMatrix);
                }
                else
                {
                    return LocalPosition;
                }
            }
            set
            {
                if (Parent != null)
                {
                    LocalPosition = Vector2.Transform(value, WorldToLocalMatrix);
                }
                else
                {
                    LocalPosition = value;
                }
            }
        }

        /// <summary>
        /// The rotation of the transform in world space in radians.
        /// </summary>
        public float Rotation
        {
            get
            {
                if (Parent != null)
                {
                    return LocalRotation + Parent.Rotation;
                }
                else
                {
                    return LocalRotation;
                }
            }
            set
            {
                if (Parent != null)
                {
                    LocalRotation = value + Parent.Rotation;
                }
                else
                {
                    LocalRotation = value;
                }
            }
        }

        /// <summary>
        /// The global scale of the object (Read Only).
        /// </summary>
        public Vector2 LossyScale { get { throw new NotImplementedException(); } }

        /// <summary>
        /// The parent of the transform.
        /// </summary>
        public Transform Parent
        {
            get
            {
                if (GameObject.Parent != null)
                    return GameObject.Parent.Transform;
                else
                    return null;
            }
        }

        /// <summary>
        /// Matrix that transforms a point from world space into local space (Read Only).
        /// </summary>
        public Matrix WorldToLocalMatrix { get { return Matrix.Invert(LocalToWorldMatrix); } }

        /// <summary>
        /// Matrix that transforms a point from local space into world space (Read Only).
        /// </summary>
        public Matrix LocalToWorldMatrix 
        { 
            get 
            {
                if (Parent != null)
                    return (Matrix.CreateScale(LocalScale.X, LocalScale.Y, 1) * Matrix.CreateRotationZ(LocalRotation) * Matrix.CreateTranslation(LocalPosition.X, LocalPosition.Y, 0)) * Parent.LocalToWorldMatrix;
                else
                    return (Matrix.CreateScale(LocalScale.X, LocalScale.Y, 1) * Matrix.CreateRotationZ(LocalRotation) * Matrix.CreateTranslation(LocalPosition.X, LocalPosition.Y, 0));
            } 
        }

        /// <summary>
        /// Returns the topmost transform in the hierarchy.
        /// (This never returns null, if this Transform doesn't have a parent it returns itself.) 
        /// </summary>
        public Transform Root
        {
            get
            {
                // start with this as root
                Transform root = this;
                // get the parent
                Transform parent = this.Parent;
                // keep going until no more parents
                while (parent != null)
                {
                    // save the parent
                    root = parent;
                    // get the parent of the parent
                    parent = parent.Parent;
                }
                return root;
            }
        }

        /// <summary>
        /// The number of children the Transform has.
        /// </summary>
        public int ChildCount { get { return GameObject.Children.Count; } }

        public Transform()
            : base("Transform")
        {
            LocalScale = new Vector2(1);
        }

        /// <summary>
        /// Unparents all children.
        /// </summary>
        public void DetachChildren()
        {
            foreach (GameObject item in GameObject.Children)
            {
                item.Parent = null;
            }
        }

        /// <summary>
        /// Moves the transform in the direction and distance of translation.
        /// </summary>
        /// <param name="translation"></param>
        /// <param name="relativeTo"></param>
        public void Translate(Vector2 translation, Space relativeTo = Space.Self)
        {
            if (relativeTo == Space.Self)
                    Position += translation;
            else
                    LocalPosition += translation;
        }

        public void Translate(float x, float y, Space relativeTo = Space.Self)
        {
             if (relativeTo == Space.Self)
                LocalPosition += new Vector2(x, y);
             else
                Position += new Vector2(x, y);
        }

        public void Translate(Vector2 translation, Transform relativeTo)
        {
            if (relativeTo != null)
                this.Position += relativeTo.TransformDirection(translation);
            else
                this.Position += translation;
        }

        public void Translate(float x, float y, Transform relativeTo)
        {
            if (relativeTo != null)
                this.Position += relativeTo.TransformDirection(new Vector2(x, y));
            else
                this.Position += new Vector2(x, y);
        }

        public Vector2 TransformDirection(Vector2 direction)
        {
            // Is this right? Needs tested.
            return Vector2.TransformNormal(direction, LocalToWorldMatrix);
        }

        public Vector2 TransformDirection(float x, float y)
        {
            return this.TransformDirection(new Vector2(x, y));
        }

        public void Rotate(float angle, Space relativeTo)
        {
            if (relativeTo == Space.Self)
                LocalRotation += angle;
            else
                Rotation += angle;
        }

        public void Rotate(float angle)
        {
            Space self = Space.Self;
            this.Rotate(angle, self);
        }

        internal void GetLocalMatrix(out Matrix localMatrix)
        {
            localMatrix = (Matrix.CreateScale(LocalScale.X, LocalScale.Y, 1) * Matrix.CreateRotationZ(LocalRotation) * Matrix.CreateTranslation(LocalPosition.X, LocalPosition.Y, 0));
        }

        internal Transform GetChild(int p)
        {
            return GameObject.Children[p].Transform;
        }

        #region IEnumerable<Transform> Members

        IEnumerator<Transform> IEnumerable<Transform>.GetEnumerator()
        {
            return new TransformEnumerator(this);
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return new TransformEnumerator(this);
        }

        #endregion
    }

    

    public struct TransformEnumerator : IEnumerator<Transform>
    {
        // Fields
        private int currentIndex;
        private Transform outer;

        // Methods
        internal TransformEnumerator(Transform outer)
        {
            this.currentIndex  = -1;
            this.outer = outer;
        }

        public bool MoveNext()
        {
            int childCount = this.outer.ChildCount;
            return (++this.currentIndex < childCount);
        }

        public void Reset()
        {
            this.currentIndex = -1;
        }

        // Properties
        public object Current
        {
            get
            {
                return this.outer.GetChild(this.currentIndex);
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            
        }

        #endregion

        #region IEnumerator<Transform> Members

        Transform IEnumerator<Transform>.Current
        {
            get
            {
                return this.outer.GetChild(this.currentIndex);
            }
        }

        #endregion
    }
}
